##############################################
# THIS MODULE IS DEPRECATED AND NO LONGER IN USE. ALL RELATIONSHIPS ARE CREATED
# IN PAGURE OWN'S DATABASE
##############################################

from . import settings

from sqlalchemy     import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session

# The SQLAlchemy "Engine" is the "starting point" for using SQLAlchemy. In this
# plugin we do not reuse the Pagure database session, because we're using a
# separate database (we do not create new tables in the Pagure database in
# order to keep the plugin completely separate). A new session for querying the
# database is started at the beginning of every incoming HTTP request, or when
# a Celery task is ran.
# 
# Useful documentation for SQLAlchemy to understand how the plugin is set up:
#     https://docs.sqlalchemy.org/en/13/orm/contextual.html
engine = create_engine(settings.DATABASE, echo=True, pool_recycle=3600)

def start_database_session():
    """
    Get a new SQLAlchemy work session. The Session object is used to query the
    database. To end a session simply call .remove() on the session object that
    is returned from this function.
    
    sessionmaker() is a SQLAlchemy session factory.
    scoped_session() is a SQLAlchemy registry of session objects. By
        using this registry, if somewhere else in the same request were
        to create a new session, the *same* object will be returned instead
        of starting a new one. Sessions are thread-local (2 different flask
        requests will start different sessions).
    """
    
    return scoped_session(sessionmaker(bind=engine))




#### MODEL




# Useful documentation for SQLAlchemy ORM:
#     https://docs.sqlalchemy.org/en/13/orm/tutorial.html
#     https://docs.sqlalchemy.org/en/13/orm/basic_relationships.html

import pagure.lib.model

from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, \
                       LargeBinary, UnicodeText, func
from sqlalchemy import PrimaryKeyConstraint
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

from . import activitypub
from . import settings
from Crypto.PublicKey import RSA

# We use SQLAlchemy "declarative" mappings: https://docs.sqlalchemy.org/en/13/orm/tutorial.html#declare-a-mapping
BASE = declarative_base()

class GpgKey(BASE):
    """
    This class represents an Actor GPG key that is used to sign HTTP POST
    requests.
    """
    
    __tablename__ = 'forgefed_gpg_key'
    
    # The URI of the key
    uri = Column(UnicodeText, primary_key=True, nullable=False)
    
    # The actor who owns the key
    actor_uri = Column(UnicodeText, nullable=False)
    
    # The private part of the key
    private = Column(LargeBinary, nullable=False)
    
    # The public part of the key
    public = Column(LargeBinary, nullable=False)
    
    # When was this key created
    created = Column(DateTime, nullable=False, default=func.now())
    
    @property
    def jsonld(self):
        return {
            '@context':      activitypub.jsonld_context,
            'id':            self.uri,
            'type':          'CryptographicKey',
            'owner':         self.actor_uri,
            #'created':      None,
            #'expires':      None,
            #'revoked':      None,
            #'privateKeyPem': self.private.decode('UTF-8'), DO NOT DISPLAY PRIVATE KEY
            'publicKeyPem':  self.public.decode('UTF-8')
        }
    
    @staticmethod
    def new_key():
        """
        Returns a new private/public key pair.
        """
        
        key = RSA.generate(settings.HTTP_SIGNATURE_KEY_BITS)
        
        return {
            'private': key.export_key('PEM'),
            'public':  key.publickey().export_key('PEM')
        }
    
    @staticmethod
    def test_or_set(actor_uri, key_uri):
        """
        Test if an Actor already has a GPG key, otherwise automatically generate
        a new one.
        
        :param actor_id: ID (URL) of the ActivityPub Actor.
        """
        
        db = start_database_session()
        
        key = db.query(GpgKey) \
                .filter(GpgKey.uri == key_uri) \
                .one_or_none()
        
        if not key:
            key = GpgKey.new_key()
            
            db.add(GpgKey(uri       = key_uri,
                          actor_uri = actor_uri,
                          private   = key['private'],
                          public    = key['public']))
            
            db.commit()
        
        db.remove()

class Activity(BASE):
    """
    This class represents an ActivityPub Activity.
    """
    
    __tablename__ = 'forgefed_activity'
    
    # The URI of the Activity.
    uri = Column(UnicodeText, primary_key=True, default=None)
    
    # The URI of the Actor who sent the Activity.
    actor_uri = Column(UnicodeText, nullable=False)
    
    # The JSON-LD document of the Activity.
    document = Column(UnicodeText, nullable=False)
    
    # When the Activity was stored.
    stored = Column(DateTime, nullable=False, default=func.now())

class Collection(BASE):
    """
    This class represents a list of items for a Collection.
    """
    
    __tablename__ = 'forgefed_collection'
    __table_args__ = (
        PrimaryKeyConstraint('uri', 'item'),
    )
    
    # The URI of the Collection.
    uri = Column(UnicodeText, nullable=False)
    
    # The URI of an item in the Collection.
    item = Column(UnicodeText, nullable=False)
    
    # When was this item added to the collection
    added = Column(DateTime, nullable=False, default=func.now())

class SameAs(BASE):
    """
    This class is used to keep links between local URIs and remote URIs.
    The reason is that we need to create local objects in Pagure to
    represents remote objects such as comments, so we keep track of which
    local objects are only a local representation of remote ones.
    """
    
    __tablename__ = 'forgefed_sameas'
    
    local_uri = Column(UnicodeText, primary_key=True, nullable=False)
    remote_uri = Column(UnicodeText, nullable=False)

class Cache(BASE):
    """
    This is a table used for caching copies of remote actors' JSONLD documents.
    It's only used when we want to retrieve information about an object
    quickly without fetching it. For example when displaying an actor's
    followers, we don't want to make 100s of GET requests just to retrieve
    the actors name.
    """
    
    __tablename__ = 'forgefed_cache'
    
    # The URI of the object.
    uri = Column(UnicodeText, primary_key=True, default=None)
    
    # The JSON-LD document of the object.
    document = Column(UnicodeText, nullable=False)

class Feed(BASE):
    """
    This class is used to store feeds about federation events. These feeds are
    simply displayed to the user in their homepage.
    """
    
    __tablename__ = 'forgefed_feed'
    
    # This only exists because SQLAlchemy requires a primary key, but this
    # primary key is not used anywhere.
    id = Column(Integer, primary_key=True)
    
    # The URI of the local Actor this feed item belongs to.
    actor_uri = Column(UnicodeText, nullable=False)
    
    # The content of the feed.
    content = Column(UnicodeText, nullable=False)
    
    # When the feed item was created.
    created = Column(DateTime, nullable=False, default=func.now())

# Automatically create the database tables on startup. The tables are first
# checked for existence before any CREATE TABLE command is issued.
# https://docs.sqlalchemy.org/en/13/orm/tutorial.html#create-a-schema
BASE.metadata.create_all(engine)
